/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "ModelSkeleton.h"
#include <xml/Stream.h>
#include <logs/Log.h>

#define SPEED_TEST

namespace liba
{
namespace lib3d
{
namespace node
{

const Atom ModelSkeleton::Joint::node_name("Joint");

void ModelSkeleton::HaMod::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back( SweetPair(ha, new model::ModelInHardwareObject(ha->create_vertex_buffer(), 0)) );
}

ModelSkeleton::HaMod::HaMod()
{
	hard::Manager::add_dependent( this );
}

ModelSkeleton::HaMod::~HaMod()
{
	hard::Manager::remove_dependent( this );
}

ModelSkeleton::Joint::Joint(ModelSkeleton * model, Joint * parent)
:	parent( parent ),
	model( model ),
	index( 0 ),
	orientation( 0, 0, 0 ),
	NodeBase( Atom() )
{}

Matrix ModelSkeleton::Joint::get_joint_matrix()
{
	Matrix mat = Matrix::translate( position );
	if( parent )
		mat *= Matrix::invert( Matrix::scale( parent->GetScale() ) );
	mat *= Matrix::rotate( orientation );
	mat *= Matrix::rotate( rotation );
	mat *= Matrix::scale( scale );
	return mat;
}

Matrix ModelSkeleton::Joint::start_add_influence(const Matrix & mat)
{
	total_mat = mat;
	return diff_mat = total_mat * Matrix::invert(initial_total_mat);
//	total_mat = Matrix::identity();
//	return total_mat * Matrix::invert(initial_mat);
}

Matrix ModelSkeleton::Joint::add_influence()
{

	total_mat = parent->total_mat * get_joint_matrix();
	return diff_mat = total_mat * Matrix::invert(initial_total_mat);
//	Matrix dmat =
//	total_mat = parent->total_mat * get_matrix();
//	return total_mat * Matrix::invert(initial_total_mat);
}

Matrix ModelSkeleton::Joint::start_first_influence(const Matrix & mat)
{
	return initial_total_mat = mat;
}

Matrix ModelSkeleton::Joint::first_influence()
{
	return initial_total_mat = parent->initial_total_mat * get_joint_matrix();
}

bool ModelSkeleton::Joint::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if( name == "Index" )
	{
		return converter::convert( value, &index );
	}
	if( name == "Orientation" )
		return converter::convert( value, &orientation );
	return NodeBase::on_attribute( prov, name, value );
}

bool ModelSkeleton::Joint::on_node( xml::Provider * prov, const std::string & name )
{
	if( name == "Joint" )
	{
		model->joints.push_back( new Joint( model, this ) );
		return prov->parse_object( model->joints.back() );
	}
	return NodeBase::on_node( prov, name );
}


animate::Node * ModelSkeleton::Joint::FindAnimateNode( const Atom & name, bool recursion )
{
	Joints::iterator it = model->joints.begin();
	for( ; it != model->joints.end(); ++it )
	{
		if( (*it)->GetName() == name && (*it)->parent == this )
			return *it;
		if ( recursion )
		{
			animate::Node * node = (*it)->FindAnimateNode( name, recursion );
			if ( node )
				return node;
		}
	}
	return 0;
}

const Atom ModelSkeleton::node_name("ModelSkeleton");

ModelSkeleton::ModelSkeleton( const Atom & name, Node * parent )
:	Model( name, parent ),
	last_index( 0 )
{
	node_type = node_name;
	joints.push_back( new Joint(this, 0) );
}

ModelSkeleton::~ModelSkeleton()
{}

bool ModelSkeleton::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if ( name == "Source" )
	{
		normal_model_src.load_rep( value.c_str() );
		return true;
	}
	if ( name == "Shadow" )
	{
		shadow_model_src.load_rep( value.c_str() );
		return true;
	}
	if( Model::on_attribute( prov, name, value ) )
		return true;
	return joints[0]->on_attribute( prov, name, value );
}

bool ModelSkeleton::on_node( xml::Provider * prov, const std::string & name )
{
	if( Model::on_node( prov, name ) )
		return true;
	return joints[0]->on_node( prov, name );
}

bool ModelSkeleton::on_end_node( xml::Provider * prov )
{
	Joints::iterator jit = joints.begin();
	(*jit)->ChangePosition( GetPosition() );
	(*jit)->ChangeRotation( GetRotation() );
	(*jit)->ChangeScale( GetScale() );
	Matrix mat0 = (*jit)->start_first_influence( (*jit)->get_joint_matrix() );
	Vector sh0 = mat0.shift();
	Atom name0 = (*jit)->GetName();
	++jit;
	for( ; jit != joints.end(); ++jit )
	{
		Matrix mat = (*jit)->first_influence();
		Vector sh = mat.shift();
		Atom name = (*jit)->GetName();
		last_index = std::max( last_index, (*jit)->get_index() );
	}
	return true;
}

animate::Node * ModelSkeleton::FindAnimateNode( const Atom & name, bool recursion )
{
	animate::Node * anode = joints[0]->FindAnimateNode( name, recursion );
	if( anode )
		return anode;
	return Model::FindAnimateNode( name, recursion );
}

void ModelSkeleton::RenderToList( hard::Hardware * hardware, hard::RenderList * render_list )
{
	hard::RenderModel model_ref;
	model_ref.world = parent->GetGlobalMatrix();

	std::vector<Matrix> joint_matrices( last_index + 1 );

	Joints::iterator jit = joints.begin();
	(*jit)->ChangePosition( GetPosition() );
	(*jit)->ChangeRotation( GetRotation() );
	(*jit)->ChangeScale( GetScale() );
	joint_matrices[(*jit)->get_index()] = (*jit)->start_add_influence( (*jit)->get_joint_matrix() );
	++jit;
	for( ; jit != joints.end(); ++jit )
	{
		joint_matrices[(*jit)->get_index()] = (*jit)->add_influence();
	}

	if( normal_model_src.get_rep() && normal_model_src.get_rep()->is_loaded() )
	{
		model::ModelInHardwareObject * object = normal_dst.find_data(hardware);

		const SimpleArray<float> * positions = normal_model_src.get_rep()->find_value_float( model::Model::position_name );
		const SimpleArray<float> * normals = normal_model_src.get_rep()->find_value_float( model::Model::normal_name );
		const SimpleArray<float> * face_normals = normal_model_src.get_rep()->find_value_float( model::Model::face_normal_name );
		const SimpleArray<float> * u_tangents = normal_model_src.get_rep()->find_value_float( model::Model::u_tangent_name );
		const SimpleArray<float> * v_tangents = normal_model_src.get_rep()->find_value_float( model::Model::v_tangent_name );

		const SimpleArray<float> * uv0 = normal_model_src.get_rep()->find_value_float( model::Model::uv0_name );
		const SimpleArray<float> * uv1 = normal_model_src.get_rep()->find_value_float( model::Model::uv1_name );

		const SimpleArray<float> * weights[4];
		weights[0] = normal_model_src.get_rep()->find_value_float( model::Model::weights0_name );
		weights[1] = normal_model_src.get_rep()->find_value_float( model::Model::weights1_name );
		weights[2] = normal_model_src.get_rep()->find_value_float( model::Model::weights2_name );
		SimpleArray<float> weights_tmp( weights[0]->size(), 0 );
		weights[3] = &weights_tmp;
		for( int i = 0; i != weights[0]->size(); ++i )
		{
			weights_tmp[i] = 1 - (*weights[0])[i] - (*weights[1])[i] - (*weights[2])[i];
		}

		const SimpleArray<int> * indices[4];
		indices[0] = normal_model_src.get_rep()->find_value_int( model::Model::indices0_name );
		indices[1] = normal_model_src.get_rep()->find_value_int( model::Model::indices1_name );
		indices[2] = normal_model_src.get_rep()->find_value_int( model::Model::indices2_name );
		indices[3] = normal_model_src.get_rep()->find_value_int( model::Model::indices3_name );

		SimpleArray<float> dst_positions( positions->size(), 0 );
		SimpleArray<float> empty_float;
		SimpleArray<int> empty_int;
		int v_count = positions->size()/3;
		for( int i = 0; i < v_count; ++i )
		{
			Vector v( (*positions)[i*3], (*positions)[i*3+1], (*positions)[i*3+2] );
			Vector p(0, 0, 0);
			for( int ind = 0; ind != 4; ++ind )
			{
				int sk = (*indices[ind])[i];
				p += (joint_matrices[sk] * v) * (*weights[ind])[i];
			}
			dst_positions[i*3] = p.x;
			dst_positions[i*3+1] = p.y;
			dst_positions[i*3+2] = p.z;
		}

		if( !object->vb->update( dst_positions, *face_normals,
							*u_tangents, *v_tangents,
							*uv0, *uv1,
							empty_float, empty_float, empty_float,
							empty_int, empty_int, empty_int, empty_int) )
		{
			THROW(Gluck(__FUNCTION__, "object->vb->update() failed" ));
		}

		model_ref.vb = object->vb;

		material::Shaders::const_iterator sit = get_shaders().begin();
		int primitive_pos = 0;
		int vertex_pos = 0;
		for( ; sit != get_shaders().end(); ++sit )
		{
			int primitive_count = (*sit)->get_triangles_count();
			int vertex_count = (*sit)->get_vertex_count();

			hard::RenderModel render_model( model_ref );
			render_model.primitive_start = primitive_pos;
			render_model.primitive_count = primitive_count;
			logs::log() << "Primitive Count for Normal: " << render_model.primitive_count << "\n";
			render_model.ib = object->ib;
			if( object->ib )
			{
				render_model.vertex_start = vertex_pos;
				render_model.vertex_count = vertex_count;
			}

			(*sit)->render_to_list( hardware, &render_model.material );

			render_list->models.push_back( render_model );
			primitive_pos += primitive_count*3;
			vertex_pos += vertex_count;
		}
	}
	if( shadow_model_src.get_rep() && shadow_model_src.get_rep()->is_loaded() )
	{
#ifdef SPEED_TEST
	__int64 now0 = threads::get_precise_tick_count();
#endif
		model::ModelInHardwareObject * object = shadow_dst.find_data(hardware);

		const SimpleArray<float> * positions = shadow_model_src.get_rep()->find_value_float( model::Model::position_name );
		const SimpleArray<float> * normals = shadow_model_src.get_rep()->find_value_float( model::Model::normal_name );

		const SimpleArray<float> * weights[4];
		weights[0] = shadow_model_src.get_rep()->find_value_float( model::Model::weights0_name );
		weights[1] = shadow_model_src.get_rep()->find_value_float( model::Model::weights1_name );
		weights[2] = shadow_model_src.get_rep()->find_value_float( model::Model::weights2_name );

		const SimpleArray<int> * indices[4];
		indices[0] = shadow_model_src.get_rep()->find_value_int( model::Model::indices0_name );
		indices[1] = shadow_model_src.get_rep()->find_value_int( model::Model::indices1_name );
		indices[2] = shadow_model_src.get_rep()->find_value_int( model::Model::indices2_name );
		indices[3] = shadow_model_src.get_rep()->find_value_int( model::Model::indices3_name );
#ifdef SPEED_TEST
	int passed0 = threads::get_precise_tick_count() - now0;
	logs::log() << "Skeleton: find_values" << passed0 * threads::get_precise_tick_time()*1000 << " msec\n";
#endif
#ifdef SPEED_TEST
	__int64 now1 = threads::get_precise_tick_count();
#endif
		SimpleArray<float> weights_tmp( weights[0]->size(), 0 );
		weights[3] = &weights_tmp;
//		for( int i = 0; i != weights[0]->size(); ++i )
//		{
//			weights_tmp[i] = 1 - (*weights[0])[i] - (*weights[1])[i] - (*weights[2])[i];
//		}
//		below is the optimization of the code above
		if( weights[0]->size() )
		{
			const float * w0 = weights[0]->data();
			const float * w0end = w0 + weights[0]->size();
			const float * w1 = weights[1]->data();
			const float * w2 = weights[2]->data();
			float * wt = weights_tmp.data();
			do
			{
				*wt++ = 1 - *w0++ - *w1++ - *w2++;
			}while(w0 != w0end);
		}
#ifdef SPEED_TEST
	int passed1 = threads::get_precise_tick_count() - now1;
	logs::log() << "Skeleton: calc 4th weight" << passed1 * threads::get_precise_tick_time()*1000 << " msec\n";
#endif
#ifdef SPEED_TEST
	__int64 now2 = threads::get_precise_tick_count();
#endif
		SimpleArray<float> dst_positions( positions->size(), 0 );
		SimpleArray<float> empty_float;
		SimpleArray<int> empty_int;
		int v_count = positions->size()/3;
		for( int i = 0; i < v_count; ++i )
		{
			Vector v( (*positions)[i*3], (*positions)[i*3+1], (*positions)[i*3+2] );
			Vector p(0, 0, 0);
			for( int ind = 0; ind != 4; ++ind )
			{
				int sk = (*indices[ind])[i];
				p += (joint_matrices[sk] * v) * (*weights[ind])[i];
			}
			dst_positions[i*3] = p.x;
			dst_positions[i*3+1] = p.y;
			dst_positions[i*3+2] = p.z;
		}
#ifdef SPEED_TEST
	int passed2 = threads::get_precise_tick_count() - now2;
	logs::log() << "Skeleton: calc positions" << passed2 * threads::get_precise_tick_time()*1000 << " msec\n";
#endif
#ifdef SPEED_TEST
	__int64 now3 = threads::get_precise_tick_count();
#endif
		if( !object->vb->update( dst_positions, *normals,
							empty_float, empty_float,
							empty_float, empty_float,
							empty_float, empty_float, empty_float,
							empty_int, empty_int, empty_int, empty_int) )
		{
			THROW(Gluck(__FUNCTION__, "object->vb->update() failed" ));
		}
#ifdef SPEED_TEST
	int passed3 = threads::get_precise_tick_count() - now3;
	logs::log() << "Skeleton: object->vb->update" << passed3 * threads::get_precise_tick_time()*1000 << " msec\n";
#endif

///		total_vertices = positions->size() / 3;
///		if( index_array->size() )
///			total_primitives = index_array->size() / 3;
///		else
///			total_primitives = positions->size() / 9;

		hard::RenderModel render_model( model_ref );
		render_model.vb = object->vb;
///		render_model.bounding_sphere = get_rep()->get_bounding_sphere();
		render_model.primitive_start = 0;
		render_model.primitive_count = positions->size() / 9;
//		logs::log() << "Primitive Count for Shadow: " << render_model.primitive_count << "\n";
		render_model.ib = object->ib;
		render_model.vertex_start = 0;
		render_model.vertex_count = positions->size() / 3;

///		(*sit)->render_to_list( hardware, &render_model.material );

#ifndef IPHONE_VERSION
		render_list->shadow_models.push_back( render_model );
#endif
	}
}

bool ModelSkeleton::on_skeleton_change()
{
	return false;
}

} // namespace node
} // namespace lib3d
} // namespace liba

